package wv;
import java.io.*;
import java.util.*;

/**
 * Automatically create testsuites by combining a set of testclasses.
 * 
 * @author Anna-Elisabeth Schnell, Arne Van der Stappen, Stefanie Verhulst
 */
public class SuiteCreator {

	/**
	 * Create all suites.
	 * 
	 * @param	packageName
	 * 			The packagename of the suites.
	 * 			This *MUST* be the package of the individual testclasses or
	 * 			a subpackage.
	 * @param	classPrefix
	 * 			The prefix of the classnames for the tests.
	 * @param 	classes
	 * 			The individual testclasses.
	 * @param	redundancies
	 * 			A map of redundancies between the tests.
	 * 			A redundancy occurs if when the test in the key passes, the
	 * 			tests in the value will also pass.
	 */
	public static void createSuites(String packageName, String classPrefix, List<Class<?>> classes, Map<Class<?>, List<Class<?>>> redundancies)
	{
		try{
			String packageCopy = packageName;
			String packageDir = "src/" + packageCopy.replace(".", "/") + "/";
					
			List<List<Integer>> ids = getAllCombinations(classes.size());
			
			for (List<Integer> idlist : new ArrayList<List<Integer>>(ids)) {
				if (missesRedundancy(idlist, redundancies, classes)) {
					ids.remove(idlist);
				}
			}
			
			for (List<Integer> idlist : ids) {
				StringBuilder string = new StringBuilder();
				string.append(classPrefix + idlist.size());
				for (Integer id : idlist) {
					Integer id2 = id + 1;
					string.append("_" + id2.toString());
				}
				
				FileWriter fstream = new FileWriter(packageDir + string.toString() + ".java");
				BufferedWriter out = new BufferedWriter(fstream);
				out.write("package "+packageName+";\n");
				out.write("\n");
				out.write("import org.junit.runner.RunWith;\n");
				out.write("import org.junit.runners.Suite;\n");
				out.write("import org.junit.runners.Suite.SuiteClasses;\n");
				out.write("\n");
				out.write("@RunWith(Suite.class)\n");
				out.write("@SuiteClasses({ \n");
				Iterator<Integer> it = idlist.iterator();
				while(it.hasNext()) {
					Integer id = it.next();
					StringBuilder classstring = new StringBuilder();
					classstring.append(classes.get(id).getName() + ".class");
					if (it.hasNext()) {
						classstring.append(",");
					} else {
						classstring.append(" })");
					}
					out.write(classstring.toString() + "\n");
				}
				out.write("public class " + string.toString() + " {\n");
				out.write("\n");
				out.write("}\n");
				
				//Close the output stream
				out.close();
			}
			
			System.out.println(ids.size());

		}catch (Exception e){//Catch exception if any
			System.err.println("Error: " + e.getMessage());
		}
	}

	/**
	 * Check if a combination is redundant.
	 * 
	 * @param	idlist
	 * 			The id's of the classes in the combination.
	 * @param 	redundancies
	 * 			A map of the redundancies
	 * @param	classes
	 * 			The list of the testsuites.
	 * @return	TRUE if the combination is redundant. FALSE otherwise.
	 */
	private static boolean missesRedundancy(List<Integer> idlist,
			Map<Class<?>, List<Class<?>>> redundancies, List<Class<?>> classes) {
		for (Integer id : idlist) {
			if (redundancies.containsKey(classes.get(id))) {
				for (Class<?> className : redundancies.get(classes.get(id)))
					if (!idlist.contains(classes.indexOf(className)))
						return true;
			}
		}
		return false;
	}

	/**
	 * Make all possible combinations.
	 * 
	 * @param	numberOfClasses
	 * 			The number of classes to combine.
	 * @return	List of id's of the classes combined.
	 */
	private static List<List<Integer>> getAllCombinations(int numberOfClasses) {
		List<List<Integer>> result = new ArrayList<List<Integer>>();
		for (int i = 2; i <= numberOfClasses; i++) {
			result.addAll(combinations(i, 0, numberOfClasses));
		}
		return result;
	}
	
	/**
	 * Make all possible combinations.
	 * 
	 * @param	num
	 * 			The number of id's to combine.
	 * @param	start
	 * 			The lowest id that can be combined.
	 * @param 	numElements
	 * 			The total number of elements to combine.
	 * @return	Lists of ID's.
	 */
	private static List<List<Integer>> combinations(int num, int start, int numElements) {
		List<List<Integer>> result = new ArrayList<List<Integer>>();
		if (num <= 0)
			return result;
		
		for (int id = start; id <= numElements - num; id++)
		{
			List<List<Integer>> sublists = combinations(num - 1, id + 1, numElements);
			if (sublists.size() == 0) {
				List<Integer> list = new ArrayList<Integer>();
				list.add(id);
				result.add(list);
			}
			else {
				for (List<Integer> sublist : sublists) {
					sublist.add(0, id);
					result.add(sublist);
				}
			}
		}
		return result;
	}
}
